home *** CD-ROM | disk | FTP | other *** search
/ MacHack 2000 / MacHack 2000.toast / pc / The Hacks / MacHacksBug / Python 1.5.2c1 / Extensions / img / Lib / imgconvert.py < prev    next >
Encoding:
Python Source  |  2000-06-23  |  18.4 KB  |  601 lines

  1. """Module to handle conversion between in-core image formats."""
  2. #
  3. # Image-conversion helper classes and routines
  4. #
  5. import imgformat
  6. import imgcolormap
  7. import imageop
  8. import imgop
  9.  
  10. error = imgformat.error
  11. unsupported_error = 'imgconvert.unsupported_error'
  12.  
  13. HIGH_QUALITY=1
  14. TRACE=0
  15.  
  16. import os
  17. if os.environ.has_key('FASTDITHER'):
  18.         HIGH_QUALITY=0
  19.  
  20. def setquality(onoff):
  21.     """Call with zero parameter to disable high-quality conversions"""
  22.     
  23.     global HIGH_QUALITY
  24.  
  25.     old_q = HIGH_QUALITY
  26.     HIGH_QUALITY = onoff
  27.     return old_q
  28.  
  29. def settrace(onoff):
  30.     """Call with non-zero parameter to enable conversion trace printing"""
  31.     
  32.     global TRACE
  33.  
  34.     old_t = TRACE
  35.     TRACE = onoff
  36.     return old_t
  37.  
  38. _colormaps = {}
  39.  
  40. #
  41. # getfmtcolormap - Create a colormap for an 8-bit format
  42. def getfmtcolormap(fmt):
  43.         """Return a colormap suitable for the 8-bit format argument"""
  44.         
  45.         if _colormaps.has_key(fmt):
  46.                 return _colormaps[fmt]
  47.                 
  48.         try:
  49.                 size = fmt.descr['size']
  50.         except AttributeError:
  51.                 raise error, 'Argument must be an image-format object'
  52.         if size <> 8:
  53.                 raise error, 'Only 8-bit formats supported'
  54.         comp = fmt.descr['comp']
  55.         if len(comp) not in (1, 3):
  56.                 raise error, 'Only 1- or 3-component formats supported'
  57.                 
  58.         map = imgcolormap.new('\0\0\0\0'*256)
  59.         if len(comp) == 1:
  60.                 for i in range(1<<comp[0][1]):
  61.                         map[i] = (i, i, i)
  62.         else:
  63.                 rmax = 1<<comp[0][1]
  64.                 gmax = 1<<comp[1][1]
  65.                 bmax = 1<<comp[2][1]
  66.                 for r in range(rmax):
  67.                         rv = r*255/(rmax-1)
  68.                         for g in range(gmax):
  69.                                 gv = g*255/(gmax-1)
  70.                                 for b in range(bmax):
  71.                                         bv = b*255/(bmax-1)
  72.                                         i = (r<<comp[0][0]) | (g<<comp[1][0]) | (b<<comp[2][0])
  73.                                         map[i] = (rv, gv, bv)
  74.         _colormaps[fmt] = map
  75.         return map        
  76.  
  77. #
  78. # _reverse - Convert top-to-bottom to bottom-to-top vv.
  79. #
  80. def _reverse(data, reader, srcfmt, dstfmt):
  81.     import string
  82.     if srcfmt.descr['size'] <> dstfmt.descr['size'] or \
  83.        srcfmt.descr['align'] <> dstfmt.descr['align']:
  84.         raise error, 'Incompatible formats'
  85.     width = reader.width
  86.     # convert width-in-pixels to width-in-bytes, taking alignment into account
  87.     align = srcfmt.descr['align'] / 8 - 1
  88.     srcsize = srcfmt.descr['size']
  89.     width = ((width * srcsize + 7) / 8 + align) & ~align
  90.     height = len(data) / width
  91.     if height*width <> len(data):
  92.         raise error, 'Incorrect datasize'
  93.     rv = []
  94.     pos = len(data)-width
  95.     while pos >= 0:
  96.         rv.append(data[pos:pos+width])
  97.         pos = pos - width
  98.     return string.join(rv, '')
  99.  
  100. #
  101. # _maptorgb - Convert colormap to rgb
  102. #
  103. def _maptorgb(data, reader, srcfmt, dstfmt):
  104.     width = reader.width
  105.     return reader.colormap.map(data, width, srcfmt, dstfmt)
  106.  
  107. #
  108. # _grey2rgb - Convert greyscale to rgb
  109. #
  110. def _greytorgb(data, reader, srcfmt, dstfmt):
  111.     greymap = getfmtcolormap(srcfmt)
  112.     # We have to trick map() in believing us...
  113.     if srcfmt == imgformat.grey:
  114.         srcfmt = imgformat.colormap
  115.     elif srcfmt == imgformat.xgrey:
  116.         srcfmt = imgformat.xcolormap
  117.     elif srcfmt == imgformat.grey_b2t:
  118.         srcfmt = imgformat.grey_b2t
  119.     return greymap.map(data, reader.width, srcfmt, dstfmt)
  120.  
  121. #
  122. # _rgb2grey - Convert rgb to greyscale
  123. #
  124. def _rgbtoxgrey(data, reader, srcfmt, dstfmt):
  125.     data = imageop.rgb2grey(data, reader.width, reader.height)
  126.     return data
  127.  
  128. #
  129. # _rgb2rgb8 - Convert rgb to xrgb8, simplistic method
  130. #
  131. #def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  132. #     return imageop.rgb2rgb8(data, reader.width, reader.height)
  133.  
  134.     
  135. #
  136. # _shuffle - Convert various RGB formats to each other
  137. #
  138. def _shuffle(data, reader, srcfmt, dstfmt):
  139.     return imgop.shuffle(data, reader.width, reader.height, srcfmt, dstfmt)
  140.     
  141. #
  142. # _unpack - Unpack sub-pixel codings
  143. #
  144. def _unpack(data, reader, srcfmt, dstfmt):
  145.     return imgop.unpack(data, reader.width, reader.height, srcfmt, dstfmt)
  146.  
  147. #
  148. # _dither - Convert 8-bit grey to 1-bit grey
  149. def _dither(data, reader, srcfmt, dstfmt):
  150.     return imgop.dither(data, reader.width, reader.height, srcfmt, dstfmt)
  151.         
  152. #
  153. # _zapbits - Remove bits from an RGB color (for colormap clustering)
  154. #
  155. class Struct: pass
  156.  
  157. def _zapbits(data, reader, fmt):
  158.     # Create a new format, initially identical to the old one
  159.     newfmt = Struct()
  160.     newfmt.name = 'Scaled-down RGB format for map-clustering'
  161.     newfmt.descr = {}
  162.     for k in fmt.descr.keys():
  163.         newfmt.descr[k] = fmt.descr[k]
  164.     # Now remove one bit from each color component
  165.     components = newfmt.descr['comp']
  166.     newcomp = ()
  167.     for pos, len in components:
  168.         if len <= 1:
  169.             raise error, 'Map-clustering failed'
  170.         newcomp = newcomp + ((pos+1, len-1),)
  171.     if TRACE:
  172.         print '       Scaling RGB values to', newcomp
  173.     newfmt.descr['comp'] = newcomp
  174.     data = imgop.shuffle(data, reader.width, reader.height, fmt, newfmt)
  175.     return data, newfmt
  176.  
  177. def _scalergbdata(data, reader, srcfmt):
  178.     try:
  179.         map = imgcolormap.fromimage(data, reader.width, reader.height, srcfmt)
  180.         return data, map
  181.     except imgcolormap.error, arg:
  182.         if arg[:15] != 'Too many colors':
  183.             raise imgcolormap.error, arg
  184.     # Do color-clustering
  185.     newfmt = srcfmt
  186.     while 1:
  187.         data, newfmt = _zapbits(data, reader, newfmt)
  188.         data = imgop.shuffle(data, reader.width, reader.height,
  189.                              newfmt, srcfmt)
  190.         try:
  191.             map = imgcolormap.fromimage(data, reader.width, reader.height,
  192.                                         srcfmt)
  193.             return data, map
  194.         except imgcolormap.error:
  195.             pass
  196.  
  197. #
  198. # _maprgb - Convert RGB to xcolormap format
  199. #
  200. def _maprgb(data, reader, srcfmt, dstfmt):
  201.     data, map = _scalergbdata(data, reader, srcfmt)
  202.     reader.colormap = map
  203.     data, newfmt = map.dither(data, reader.width, reader.height,
  204.                               srcfmt, HIGH_QUALITY)
  205.     if newfmt != imgformat.xcolormap:
  206.         raise error, 'Internal error: map.dither returned format '+`newfmt`
  207.     return data
  208.     
  209. def _rgbtoxrgb8(data, reader, srcfmt, dstfmt):
  210.     map = getfmtcolormap(dstfmt)
  211.     data, newfmt = map.dither(data, reader.width, reader.height,
  212.                               srcfmt, HIGH_QUALITY)
  213.     if newfmt != imgformat.xcolormap:
  214.         raise error, 'Internal error: map.dither returned format '+`newfmt`
  215.     return data
  216.     
  217. #
  218. # _removestride - Remove the stride from an 8-bit image
  219. #
  220. def _removestride(data, reader, srcfmt, dstfmt):
  221.     import string
  222.     rv = []
  223.     dstwidth = reader.width
  224.     srcwidth = ((dstwidth+3) & ~3)
  225.     if srcwidth == dstwidth:
  226.         return data
  227.     for i in range(0, len(data), srcwidth):
  228.         rv.append(data[i:i+dstwidth])
  229.     return string.join(rv, '')
  230.  
  231. #
  232. # _addstride - Add stride to an 8-bit image
  233. #
  234. def _addstride(data, reader, srcfmt, dstfmt):
  235.     srcwidth = reader.width
  236.     dstwidth = ((srcwidth+3) & ~3)
  237.     if srcwidth == dstwidth:
  238.         return data
  239.     import string
  240.     rv = []
  241.     extra = '\0' * (dstwidth-srcwidth)
  242.     for i in range(0, len(data), srcwidth):
  243.         rv.append(data[i:i+srcwidth] + extra)
  244.     return string.join(rv, '')
  245.  
  246. #
  247. # 'lossiness' is a scalar value. Use 0 if nothing changes in the pixels,
  248. # 1 if no information is lost but bits are (grey->rgb), 2 if information
  249. # is lost (rgb->grey), 3 if the converter also produces a colormap.
  250. #
  251. _converters = [ \
  252.     (imgformat.grey,    imgformat.grey_b2t,        _reverse,        0),
  253.     (imgformat.grey_b2t,imgformat.grey,            _reverse,        0),
  254.     (imgformat.xgrey,    imgformat.xgrey_b2t,    _reverse,        0),
  255.     (imgformat.xgrey_b2t,imgformat.xgrey,        _reverse,        0),
  256.     (imgformat.colormap,imgformat.colormap_b2t, _reverse,        0),
  257.     (imgformat.colormap_b2t,imgformat.colormap, _reverse,        0),
  258.     (imgformat.rgb,        imgformat.rgb_b2t,        _reverse,        0),
  259.     (imgformat.rgb_b2t, imgformat.rgb,            _reverse,        0),
  260.     (imgformat.rgb8,    imgformat.rgb8_b2t,        _reverse,        0),
  261.     (imgformat.rgb8_b2t,imgformat.rgb8,            _reverse,        0),
  262.     (imgformat.xrgb8,    imgformat.xrgb8_b2t,    _reverse,        0),
  263.     (imgformat.xrgb8_b2t,imgformat.xrgb8,        _reverse,        0),
  264.     (imgformat.bmprgble_b2t, imgformat.bmprgble, _reverse,        0),
  265.     (imgformat.bmprgble,    imgformat.bmprgble_b2t, _reverse,    0),
  266.     (imgformat.bmprgbbe_b2t, imgformat.bmprgbbe, _reverse,        0),
  267.     (imgformat.bmprgbbe,    imgformat.bmprgbbe_b2t, _reverse,    0),
  268.     (imgformat.bmppacked,    imgformat.bmppacked_b2t, _reverse,    0),
  269.     (imgformat.bmppacked_b2t, imgformat.bmppacked, _reverse,    0),
  270.     (imgformat.bmppacked4,    imgformat.bmppacked4_b2t, _reverse,    0),
  271.     (imgformat.bmppacked4_b2t, imgformat.bmppacked4, _reverse,    0),
  272.  
  273.     (imgformat.grey,    imgformat.rgb,            _greytorgb,        1),
  274.     (imgformat.xgrey,    imgformat.rgb,            _greytorgb,        1),
  275.     (imgformat.grey_b2t,imgformat.rgb_b2t,        _greytorgb,        1),
  276.  
  277.     (imgformat.colormap,imgformat.rgb,            _maptorgb,        1),
  278.     (imgformat.xcolormap,imgformat.rgb,            _maptorgb,        1),
  279.     (imgformat.colormap_b2t,imgformat.rgb_b2t,    _maptorgb,        1),
  280.  
  281.     (imgformat.rgb,        imgformat.xgrey,        _rgbtoxgrey,    2),
  282. #     (imgformat.rgb,    imgformat.xrgb8,        _rgbtoxrgb8,    2),
  283.  
  284.     (imgformat.macrgb,    imgformat.rgb,            _shuffle,        0),
  285.     (imgformat.macrgb16,imgformat.rgb,            _shuffle,        1),
  286.     (imgformat.xfreergb16,imgformat.rgb,            _shuffle,        1),
  287.     (imgformat.rgb8,    imgformat.rgb,            _shuffle,        1),
  288.     (imgformat.xrgb8,    imgformat.rgb,            _shuffle,        1),
  289.     (imgformat.rgb,        imgformat.macrgb,        _shuffle,        0),
  290.     (imgformat.macrgb16,imgformat.macrgb,        _shuffle,        1),
  291.     (imgformat.xfreergb16,imgformat.macrgb,        _shuffle,        1),
  292.     (imgformat.rgb8,    imgformat.macrgb,        _shuffle,        1),
  293.     (imgformat.xrgb8,    imgformat.macrgb,        _shuffle,        1),
  294.     (imgformat.rgb,        imgformat.macrgb16,        _shuffle,        2),
  295.     (imgformat.macrgb,    imgformat.macrgb16,        _shuffle,        2),
  296.     (imgformat.rgb8,    imgformat.macrgb16,        _shuffle,        1),
  297.     (imgformat.xrgb8,    imgformat.macrgb16,        _shuffle,        1),
  298.     (imgformat.rgb,        imgformat.xfreergb16,        _shuffle,        2),
  299.     (imgformat.macrgb,    imgformat.xfreergb16,        _shuffle,        2),
  300.     (imgformat.rgb8,    imgformat.xfreergb16,        _shuffle,        1),
  301.     (imgformat.xrgb8,    imgformat.xfreergb16,        _shuffle,        1),
  302.     
  303.     # These are incomplete, many more are possible
  304.     (imgformat.bmprgbbe,        imgformat.rgb,    _shuffle,        1),
  305.     (imgformat.bmprgble,        imgformat.rgb,    _shuffle,        1),
  306.     (imgformat.bmprgbbe,        imgformat.macrgb,        _shuffle,        1),
  307.     (imgformat.bmprgble,        imgformat.macrgb,        _shuffle,        1),
  308.     (imgformat.bmprgbbe,        imgformat.macrgb16,        _shuffle,        2),
  309.     (imgformat.bmprgble,        imgformat.macrgb16,        _shuffle,        2),
  310.     (imgformat.bmprgbbe,        imgformat.xfreergb16,        _shuffle,        2),
  311.     (imgformat.bmprgble,        imgformat.xfreergb16,        _shuffle,        2),
  312.     (imgformat.rgb,                imgformat.bmprgbbe,        _shuffle,        0),
  313.     (imgformat.rgb,                imgformat.bmprgble,        _shuffle,        0),
  314.     (imgformat.macrgb,    imgformat.bmprgbbe,        _shuffle,        0),
  315.     (imgformat.macrgb,    imgformat.bmprgble,        _shuffle,        0),
  316.     (imgformat.macrgb16,        imgformat.bmprgbbe,        _shuffle,        2),
  317.     (imgformat.macrgb16,        imgformat.bmprgble,        _shuffle,        2),
  318.     (imgformat.xfreergb16,        imgformat.bmprgbbe,        _shuffle,        2),
  319.     (imgformat.xfreergb16,        imgformat.bmprgble,        _shuffle,        2),
  320.  
  321.     (imgformat.rgb,        imgformat.xrgb8,        _rgbtoxrgb8,    2),
  322.  
  323.     (imgformat.xrgb8,    imgformat.rgb8,            _shuffle,        0),
  324.     (imgformat.rgb8,    imgformat.xrgb8,        _shuffle,        0),
  325.  
  326.     (imgformat.pbmbitmap, imgformat.grey,        _shuffle,        1),
  327.     (imgformat.pbmbitmap, imgformat.xgrey,        _shuffle,        1),
  328.     (imgformat.grey,    imgformat.pbmbitmap,    _dither,        2),
  329.     (imgformat.xgrey,    imgformat.pbmbitmap,    _dither,        2),
  330.     
  331.     (imgformat.bmppacked, imgformat.pbmbitmap,    _unpack,        2),
  332.     (imgformat.bmppacked, imgformat.grey,        _unpack,        2),
  333.     (imgformat.bmppacked, imgformat.xgrey,        _unpack,        2),
  334.     (imgformat.bmppacked, imgformat.colormap,    _unpack,        1),
  335.     (imgformat.bmppacked, imgformat.xcolormap,    _unpack,        1),
  336.     (imgformat.bmppacked4, imgformat.colormap,    _unpack,        1),
  337.     (imgformat.bmppacked4, imgformat.xcolormap,    _unpack,        1),
  338.  
  339.     (imgformat.xbmpacked, imgformat.pbmbitmap,    _unpack,        2),
  340.     (imgformat.xbmpacked, imgformat.grey,        _unpack,        2),
  341.     (imgformat.xbmpacked, imgformat.xgrey,        _unpack,        2),
  342.  
  343.     (imgformat.grey,    imgformat.xgrey,        _removestride,    0),
  344.     (imgformat.grey_b2t,imgformat.xgrey_b2t,    _removestride,    0),
  345.     (imgformat.rgb8_b2t,imgformat.xrgb8_b2t,    _removestride,    0),
  346.     (imgformat.colormap,imgformat.xcolormap,    _removestride,    0),
  347.     (imgformat.xgrey,    imgformat.grey,            _addstride,        0),
  348.     (imgformat.xgrey_b2t,imgformat.grey_b2t,    _addstride,        0),
  349.     (imgformat.xrgb8_b2t,imgformat.rgb8_b2t,    _addstride,        0),
  350.     (imgformat.xcolormap,imgformat.colormap,    _addstride,        0),
  351.  
  352.     (imgformat.rgb,        imgformat.xcolormap,    _maprgb,        3),
  353. ]
  354.  
  355. #
  356. # The converts we have built, indexed by sourceformat.
  357. # Each entry is another dictionary (indexed by dstformat).
  358. # The entries of these dictionaries are lists of [lossiness, len, [funcs]]
  359. #
  360. _generated = {}
  361.  
  362. #
  363. # Add a converter from 'srcfmt' to 'dstfmt' to the list, possibly
  364. # replacing an existing converter
  365. #
  366. def addconverter(srcfmt, dstfmt, func, lossy):
  367.         """Tell imgconvert about a new converter.
  368.         Args: source_format, dest_format, function, lossy.
  369.         lossy is 0 (not lossy), 1 (wastes bits), 2 (loses bits) or
  370.         3 (converts to colormap format).
  371.  
  372.         function is called as function(data, reader, srcfmt, dstfmt)
  373.         """
  374.         
  375.         for i in range(len(_converters)):
  376.                 isrcfmt, idstfmt, irtn, ilossy = _converters[i]
  377.                 if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  378.                         _converters[i] = (srcfmt, dstfmt, func, lossy)
  379.                         return
  380.         _converters.append((srcfmt,dstfmt,func,lossy))
  381.  
  382. #
  383. # Returns a list of conversion functions that will convert
  384. # srcfmt to dstfmt if applied in that order.
  385. #
  386. def getconverter(srcfmt, dstfmt):
  387.         """Return a converter from srcfmt to dstfmt.
  388.         A converter is a list [lossy, length, list-of-tuples],
  389.         where each tuple is (srcfmt, dstfmt, func, lossy).
  390.         Calling each of the functions in order will convert your image.
  391.         """
  392.         
  393.         global _generated
  394.         #
  395.         # If formats are the same return the dummy converter
  396.         #
  397.         if srcfmt == dstfmt: return []
  398.         #
  399.         # Otherwise, if we have a converter, return that one
  400.         #
  401.         for this in _converters:
  402.                 isrcfmt, idstfmt, irtn, ilossy = this
  403.                 if (srcfmt, dstfmt) == (isrcfmt, idstfmt):
  404.                         return [ilossy, 1, [this]]
  405.         #
  406.         # Finally, we try to create a converter
  407.         #
  408.         if not _generated.has_key(srcfmt):
  409.                 # Not there yet. Try to create it.
  410.                 _generated[srcfmt] = _enumerate_converters(srcfmt)
  411.                 
  412.         if not _generated[srcfmt].has_key(dstfmt):
  413.                 raise unsupported_error, (srcfmt, dstfmt)
  414.  
  415.         cf = _generated[srcfmt][dstfmt]
  416.         return cf
  417.  
  418. def _enumerate_converters(srcfmt):
  419.         cvs = {}
  420.         formats = [srcfmt]
  421.         steps = 0
  422.         while 1:
  423.                 workdone = 0
  424.                 for this in _converters:
  425.                         isrcfmt, idstfmt, irtn, ilossy = this
  426.                         #
  427.                         # First see if the source format is of any use.
  428.                         #
  429.                         if isrcfmt == srcfmt:
  430.                                 #
  431.                                 # This converter directly understands our
  432.                                 # source format. Remember it.
  433.                                 #
  434.                                 template = [ilossy, 1, [this]]
  435.                         elif cvs.has_key(isrcfmt):
  436.                                 #
  437.                                 # We have a path to this format, so
  438.                                 # this converter can help us further.
  439.                                 #
  440.                                 template = cvs[isrcfmt][:]
  441.                                 template[0] = max(template[0], ilossy)
  442.                                 template[1] = template[1] + 1
  443.                                 template[2] = template[2] + [this]
  444.                         else:
  445.                                 continue
  446.                         #
  447.                         # Next, check whether we want this converter
  448.                         # (if it is the first one for this dstfmt, or
  449.                         # if it is better than what we have)
  450.                         #
  451.                         if not cvs.has_key(idstfmt):
  452.                                 cvs[idstfmt] = template
  453.                                 workdone = 1
  454.                         else:
  455.                                 previous = cvs[idstfmt]
  456.                                 if template < previous:
  457.                                         cvs[idstfmt] = template
  458.                                         workdone = 1
  459.                 if not workdone:
  460.                         break
  461.                 #
  462.                 # Finally, a check for loops.
  463.                 #
  464.                 steps = steps + 1
  465.                 if steps > len(_converters):
  466.                         print '------------------loop in emunerate_converters--------'
  467.                         print 'CONVERTERS:'
  468.                         print _converters
  469.                         print 'RESULTS:'
  470.                         print cvs
  471.                         raise error, 'Internal error - loop'
  472.         return cvs
  473.  
  474. def stackreader(dstfmt, reader):
  475.     """Create a reader-like object that reads image file data and
  476.     converts it to the requested format.
  477.     Args: format, original_reader
  478.     """
  479.     
  480.     if dstfmt in reader.format_choices:
  481.         reader.format = dstfmt
  482.         return reader
  483.     # Nope, not supported directly. Find all possible converters
  484.     list = []
  485.     for srcfmt in reader.format_choices:
  486.         try:
  487.             rv = getconverter(srcfmt, dstfmt)
  488.         except unsupported_error:
  489.             continue
  490.         if rv:
  491.             [lossy, len, funclist] = rv
  492.             list.append(lossy, len, srcfmt, funclist)
  493.     if not list:
  494.         raise unsupported_error, (reader.format_choices, dstfmt)
  495.     # Now, sort and use the best
  496.     list.sort()
  497.     lossy, len, srcfmt, funclist = list[0]
  498.     if lossy == 3:
  499.         return _MapReaderStack(reader, dstfmt, srcfmt, funclist)
  500.     else:
  501.         return _ConverterStack(reader, dstfmt, srcfmt, funclist)
  502.  
  503. def stackwriter(srcfmt, writer):
  504.     """Create a writer-like object that writes an image file from source
  505.     data in the specified format.
  506.     Args: source_format, destination_writer
  507.     """
  508.     
  509.     if srcfmt in writer.format_choices:
  510.         writer.format = srcfmt
  511.         return writer
  512.     # Nope, not supported directly. Find all possible converters
  513.     list = []
  514.     for dstfmt in writer.format_choices:
  515.         try:
  516.             [lossy, len, funclist] = getconverter(srcfmt, dstfmt)
  517.         except unsupported_error:
  518.             continue
  519.         list.append(lossy, len, dstfmt, funclist)
  520.     if not list:
  521.         raise unsupported_error, (srcfmt, writer.format_choices)
  522.     # Now, sort and use the best
  523.     list.sort()
  524.     lossy, len, dstfmt, funclist = list[0]
  525.     return _ConverterStack(writer, srcfmt, dstfmt, funclist)
  526.  
  527. #
  528. # The placeholder class
  529. class _ConverterStack:
  530.     def __init__(self, base, ourfmt, basefmt, funclist):
  531.         self._base = base
  532.         self._funclist = funclist
  533.         self._copyattrtoself()
  534.         self.format_choices = (ourfmt,)
  535.         self.format = ourfmt
  536.         self._basefmt = basefmt
  537.  
  538.     def _copyattrtoself(self):
  539.         srcdict = self._base.__dict__
  540.         dstdict = self.__dict__
  541.         for k in srcdict.keys():
  542.             if k[0] <> '_':
  543.                 dstdict[k] = srcdict[k]
  544.  
  545.     def _copyattrfromself(self):
  546.         srcdict = self.__dict__
  547.         dstdict = self._base.__dict__
  548.         for k in srcdict.keys():
  549.             if k[0] <> '_':
  550.                 dstdict[k] = srcdict[k]
  551.  
  552.     def read(self):
  553.         self._copyattrfromself()
  554.         self._base.format = self._basefmt
  555.         data = self._base.read()
  556.         if TRACE:
  557.             print 'Datasize=', len(data)
  558.             print 'Converting', self._basefmt.name, 'to', self.format.name,
  559.             print 'in', len(self._funclist), 'steps:'
  560.         for f in self._funclist:
  561.             if TRACE:
  562.                 print '     ',f[0].name, 'to',f[1].name
  563.             data = apply(f[2], (data, self, f[0], f[1]))
  564.             if TRACE:
  565.                 print 'Datasize is now', len(data)
  566.         return data
  567.  
  568.     def write(self, data):
  569.         if TRACE:
  570.             print 'Converting', self.format.name, 'to', self._basefmt.name,
  571.             print 'in', len(self._funclist), 'steps:'
  572.         for f in self._funclist:
  573.             if TRACE:
  574.                 print '     ',f[0].name, 'to',f[1].name
  575.             data = apply(f[2], (data, self, f[0], f[1]))
  576.         self._copyattrfromself()
  577.         self._base.format = self._basefmt
  578.         self._base.write(data)
  579.  
  580. #
  581. # A MapReaderStack is used if one of the converters also returns a
  582. # colormap. In this case we have to read upon init to set the colormap
  583. # attribute.
  584. #
  585. class _MapReaderStack(_ConverterStack):
  586.     def __init__(self, base, ourfmt, basefmt, funclist):
  587.         _ConverterStack.__init__(self, base, ourfmt, basefmt, funclist)
  588.         self._base.format = self._basefmt
  589.         data = self._base.read()
  590.         if TRACE:
  591.             print 'Converting', self._basefmt.name, 'to', self.format.name,
  592.             print 'in', len(self._funclist), 'steps:'
  593.         for f in self._funclist:
  594.             if TRACE:
  595.                 print '     ',f[0].name, 'to',f[1].name
  596.             data = apply(f[2], (data, self, f[0], f[1]))
  597.         self._data = data
  598.  
  599.     def read(self):
  600.         return self._data
  601.